package com.k8.registry;


import com.k8.common.aware.BeanFactoryAware;
import com.k8.common.factroy.BeanFactory;
import com.k8.common.util.ObjectUtil;
import com.k8.rpc.Protocol;
import com.k8.rpc.RpcException;
import com.k8.rpc.cluster.Cluster;
import com.k8.rpc.cluster.ClusterInvoker;
import com.k8.rpc.directory.DynamicDirectory;
import com.k8.rpc.expoter.Exporter;
import com.k8.rpc.invoke.Invoker;
import com.k8.rpc.invoke.invocation.RpcInvocationTemplate;
import com.k8.rpc.invoke.invocation.RpcInvocationTemplateManager;
import com.k8.rpc.metadata.AppMetaData;
import com.k8.rpc.registry.Registry;
import com.k8.rpc.url.ApiSignature;
import com.k8.rpc.url.URL;
import com.k8.rpc.util.RpcUtils;
import com.k8.rpc.util.URLUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.k8.common.constants.CommonKeyConstants.EXPORTER_KEY;
import static com.k8.common.constants.CommonKeyConstants.RegistryKeyConstant.SERVICE_INSTANCES_KEY;
import static com.k8.common.constants.CommonKeyConstants.RpcKeyConstant.API_SIGNATURE_KEY;

public abstract class AbstractRegistryProtocol implements Protocol, BeanFactoryAware {
    /**
     * 不同于dubbo，dubbo有一个bug，在注册时除了向注册中心注册进简单的元数据
     * bug：它还向元数据中心注册该服务配置支持的所有协议的具体信息，如线程id等，但是，后一个协议会覆盖前一个协议的配置，因为dataId相同
     * 但是不影响正常调用，那么元数据中心存在的必要性存疑？
     * 故此不再注册元数据详细配置，只注册mapping和简单的元数据到注册中心
     */
    protected BeanFactory beanFactory;

    @Override
    public void beanFactoryAware(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    //registerConfigUrl配置的path应该是接口名:group:version格式，协议应该是service-discover-registry

    /**
     * @param invoker
     * @param registerConfigUrl
     * @param <T>
     * @return
     * @throws RpcException
     */
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker, URL registerConfigUrl) throws RpcException {
        Registry registry = RpcUtils.getRegistry(registerConfigUrl, beanFactory);
        URL protocolUrl = (URL) registerConfigUrl.getAttribute(EXPORTER_KEY);
        ObjectUtil.checkNotNull(protocolUrl, "URL to exporter is not put in registerConfigUrl");
        //该signature用于代表InvokerKey，由具体的协议#接口名:group:version组成
        ApiSignature signature = (ApiSignature) registerConfigUrl.getAttribute(API_SIGNATURE_KEY);
        RpcInvocationTemplateManager invocationTemplateManager = beanFactory.getOrRegistryBean(RpcInvocationTemplateManager.class);
        Exporter<T> exporter =
                doExport(invoker, registerConfigUrl, protocolUrl, registry, signature);
        Map<String, RpcInvocationTemplate> rpcInvocationTemplateMap = RpcUtils.resolveInvocationTemplates(invoker.getInterface(), exporter, invoker.getTarget());
        rpcInvocationTemplateMap.forEach(invocationTemplateManager::putInvocation);
        //doExport服务开启之后即可异步注册该服务
        exporter.register();
        return exporter;
    }


    /**
     * 负责到注册中心中的服务发现
     * url为registryConfigUrl
     */
    @Override
    public <T> Invoker<T> refer(URL url, Class<T> serviceType) throws RpcException {
        //此时协议头已经被转换，替换成具体的注册中心
        Registry registry = RpcUtils.getRegistry(url, beanFactory);
        registry.subscribe(url);
        //由应用级注册的元数据解析所得，目前仅提供不同实例为该服务提供的地址和端口，再结合每个服务在注册中心
        //唯一的针对该服务的细粒度配置进行填充，最后转换为该协议的url
        //todo 将配置中心元数据的说法修改为address，具体的配置应该参照config，服务处应该只提供满
        //满足简单的连接需求即可
        List<AppMetaData> remoteAppInstances = (List<AppMetaData>) url.getAttribute(SERVICE_INSTANCES_KEY);
        URL protocolUrl = (URL) url.getAttribute(EXPORTER_KEY);
        List<Invoker<T>> invokerList = new ArrayList<>(remoteAppInstances.size());
        Map<Long, Invoker<T>> invokerMap = new HashMap<>(remoteAppInstances.size());
        remoteAppInstances.forEach(appMetaData -> {
            //处理方式要和dynamicDirectory同步
            URL serviceInstanceUrl = URLUtils.appMetaData2Url(appMetaData, protocolUrl);
            Invoker<T> invoker = RpcUtils.buildConsumerInvoker(serviceInstanceUrl, beanFactory, serviceType);
            invokerList.add(invoker);
            long instanceId = appMetaData.getServiceInstanceId();
            invokerMap.put(instanceId, invoker);
        });
        Cluster cluster = RpcUtils.getCluster(protocolUrl, beanFactory);
        DynamicDirectory<T> directory = new DynamicDirectory<>(invokerList, invokerMap, serviceType, beanFactory, url);
        ClusterInvoker<T> clusterInvoker = cluster.join(directory);
        return clusterInvoker;
    }


    @Override
    public void destroy() {

    }

    protected abstract <T> Exporter<T> doExport(Invoker<T> invoker, URL registryUrl, URL protocolUrl, Registry registry, ApiSignature apiSignature);

}
